// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: jshin
#pragma once

#include <map>
#include <string>
#include <string_view>
#include <tuple>


#include <simdjson.h>

#include "coin/proto/coin_market_enums.pb.h"
#include "coin2/base/conversion.h"
#include "coin2/base/time.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/feed_util/book_builder.h"
#include "coin2/exchange/base/feed_util/trade.h"
#include "coin2/exchange/base/feed_util/kline.h"
#include "coin2/exchange/util/json_util.h"

namespace coin2::exchange::okex_common::feed_v3 {
namespace impl {
using coin2::exchange::base::market::ExchangeType;
using coin2::exchange::base::market::MarketType;
using coin2::exchange::base::feed::FeedUpdateManager;

class OkexCommonFeedUpdateParser {
 public:
  static void ParseBook(
      BookBuilder* book_builder,
      const simdjson::dom::element& item,
      BookBuilder::BookOption book_type) {
    auto ts = TimestampFromOkexIso8601(item["timestamp"].get<std::string_view>());
    auto tx = book_builder->NewTransaction(ts);

    if (book_type == BookBuilder::SNAPSHOT) {  // partial
      tx.Clear();
    } else if (book_type == BookBuilder::LIGHT_BBO) {
      tx.BboClear();
    }

    for (const auto& tick : item["asks"].get<simdjson::dom::array>()) {
      tx.UpdateAsk(stod(tick.at(0).get<const char*>()), stod(tick.at(1).get<const char*>()));
    }
    for (const auto& tick : item["bids"].get<simdjson::dom::array>()) {
      tx.UpdateBid(stod(tick.at(0).get<const char*>()), stod(tick.at(1).get<const char*>()));
    }
  }

  static void ParseTrade(const simdjson::dom::element& item, Trade* trade) {
    trade->timestamp = TimestampFromOkexIso8601(item["timestamp"].get<std::string_view>());
    trade->price = stod(item["price"].get<const char*>());
    auto qty = item.at_pointer("/qty");
    if (!qty.error()) {
      trade->fill_qty = stod(qty.get<const char*>());
    } else {
      trade->fill_qty = stod(item["size"].get<const char*>());
    }
    trade->side = ParseTradeSide(item["side"].get<std::string_view>());
  }

  static coin2::exchange::feed::TradeSide ParseTradeSide(std::string_view side) {
    if (side == "buy") {
      return coin2::exchange::feed::TRADE_BUY_SIDE;
    } else if (side == "sell") {
      return coin2::exchange::feed::TRADE_SELL_SIDE;
    } else {
      throw std::runtime_error("[Okex] unknown side: " + std::string(side));
    }
  }

  static bool ParseOpenInterest(
      const simdjson::dom::element& item,
      OpenInterest* open_interest) {
    open_interest->timestamp = TimestampFromOkexIso8601(item["timestamp"].get<std::string_view>());
    // found issue
    // 2021-02-17T15:39:37.418Z ETH-USD-210326 open_interest is empty
    std::string val(item["open_interest"].get<const char*>());
    if (val.empty()) {
      return false;
    }
    open_interest->open_interest_qty = stod(val);
    return true;
  }

  static void ParseMarkPrice(
      const simdjson::dom::element& item,
      MarkPrice* mark_price) {
    mark_price->timestamp = TimestampFromOkexIso8601(item["timestamp"].get<std::string_view>());
    mark_price->mark_price = stod(item["mark_price"].get<const char*>());
  }

  static void ParseFundingRate(
      const simdjson::dom::element& item,
      FundingRate* funding_rate,
      int64_t timestamp) {
    CHECK(funding_rate);
    funding_rate->Clear();
    funding_rate->timestamp = timestamp;
    funding_rate->funding_rate = stod(item["funding_rate"].get<const char*>());
    funding_rate->funding_time = TimestampFromOkexIso8601(
        item["funding_time"].get<std::string_view>());
    funding_rate->estimated_rate = stod(item["estimated_rate"].get<const char*>());
  }

  static bool Parse1Kline(
      const simdjson::dom::array& kline_data,
      Klines* klines,
      int64_t timestamp,
      fastfeed::proto::KlineInterval interval) {
    CHECK_EQ(kline_data.size(), 7);
    int64_t open_timestamp = TimestampFromOkexIso8601(kline_data.at(0).get<std::string_view>());
    auto result = klines->AddKline(open_timestamp);
    if (!result) return false;

    Kline* kline;
    bool freezed;
    std::tie(kline, freezed) = result.value();

    kline->open = stod(kline_data.at(1).get<const char*>());
    kline->high = stod(kline_data.at(2).get<const char*>());
    kline->low = stod(kline_data.at(3).get<const char*>());
    kline->close = stod(kline_data.at(4).get<const char*>());
    kline->volume = stod(kline_data.at(6).get<const char*>());
    kline->timestamp = timestamp;
    kline->interval = interval;
    return freezed;
  }

  static fastfeed::proto::KlineInterval ParseKlineInterval(std::string_view period) {
    static std::map<std::string, fastfeed::proto::KlineInterval> interval_map = {
      {"60", fastfeed::proto::KLINE_INTERVAL_1MIN},
      {"180", fastfeed::proto::KLINE_INTERVAL_3MIN},
      {"300", fastfeed::proto::KLINE_INTERVAL_5MIN},
      {"900", fastfeed::proto::KLINE_INTERVAL_15MIN},
      {"1800", fastfeed::proto::KLINE_INTERVAL_30MIN},
      {"3600", fastfeed::proto::KLINE_INTERVAL_1HOUR},
      {"86400", fastfeed::proto::KLINE_INTERVAL_1DAY},
      {"2678400", fastfeed::proto::KLINE_INTERVAL_1MON}
    };
    auto iter = interval_map.find(std::string(period));
    if (iter == interval_map.end()) {
      NOTREACHED() << "Not support interval " << period;
      return fastfeed::proto::KLINE_INTERVAL_INVALID;
    }

    return iter->second;
  }

  static std::tuple<std::string, std::string, int64_t, int>
  ExplodeUrl(std::string_view url) {
    std::string_view prefix = "instruments/";
    size_t start = url.find(prefix) + prefix.size();
    size_t end = url.find("/candles");
    std::string_view native_symbol = url.substr(start, end - start);

    std::string_view granularity_key = "granularity=";
    start = url.find(granularity_key) + granularity_key.size();
    end = url.find("&", start);
    std::string_view granularity = url.substr(start, end - start);

    std::string_view start_key = "start=";
    start = url.find(start_key) + start_key.size();
    end = url.find("&", start);
    std::string_view start_time = url.substr(start, end - start);
    int64_t start_timestamp = TimestampFromIso8601(std::string(start_time));

    int page = -1;
    std::string_view page_key = "page=";
    start = url.find(page_key);
    if (start != std::string_view::npos) {
      start += page_key.size();
      end = url.find("&", start);
      std::string_view page_view = url.substr(start, end - start);
      page = std::stoi(std::string(page_view));
    }

    return std::make_tuple(std::string(native_symbol), std::string(granularity),
        start_timestamp, page);
  }
};
}  // namespace impl
using impl::OkexCommonFeedUpdateParser;
}  // namespace coin2::exchange::okex_common::feed_v3
